package cn.edu.buaa.treehole.service;

import cn.edu.buaa.treehole.dao.SignatureDao;
import cn.edu.buaa.treehole.dao.UserSignatureDao;
import cn.edu.buaa.treehole.dao.exception.DaoException;
import cn.edu.buaa.treehole.pojo.dao.SignatureDaoInfo;
import cn.edu.buaa.treehole.pojo.dao.UserSignatureDaoInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.*;

@Service
public class SignatureService {
    public static final int SIGNATURE_MAX_LENGTH = 16;
    private static final char[] pool = new char[]{
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',

            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
            'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',

            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
            'o', 'p', 'q', 'r', 's', 't', 'u', 'v',

            'w', 'x', 'y', 'z', '0', '1', '2', '3',
            '4', '5', '6', '7', '8', '9', '!', '@',

            '#', '$', '%', '^', '&', '<', '>', '?',
            '[', ']', '{', '}', ':', ';', '|', ',',

            '(', ')', '-', '+', '=', '_', '\\', '/',
    };

    private final Random random = new Random();
    private final SignatureDao signatureDao;
    private final UserSignatureDao userSignatureDao;
    private UserService userService;
    private SignatureService signatureService;

    @Autowired
    public SignatureService(SignatureDao signatureDao, UserSignatureDao userSignatureDao) {
        this.signatureDao = signatureDao;
        this.userSignatureDao = userSignatureDao;
    }

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @Autowired
    public void setSignatureService(SignatureService signatureService) {
        this.signatureService = signatureService;
    }

    public String nextSignature(long pid) throws DaoException {
        while(true) {
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < 16; i++) {
                stringBuilder.append(pool[random.nextInt(pool.length)]);
            }
            String ret = stringBuilder.toString();
            if (signatureDao.selectAllNidByIdAndSignature(pid, ret).size() == 0) {
                return ret;
            }
        }
    }

    public boolean contain(long pid, String signature) {
        return signatureDao.selectAllNidByIdAndSignature(pid, signature).size() != 0;
    }

    public long getNid(long pid, String signature) {
        return signatureDao.selectNidByIdAndSignature(pid, signature);
    }

    private void tryAddToUser(long pid, String signature) {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            HttpSession session = request.getSession();
            long uid = userService.loginCheck(request, session);
            signatureService.addSignatureForUser(uid, pid, signature);
        } catch (Exception ignored) { }
    }

    @Transactional(rollbackFor = {DaoException.class, RuntimeException.class})
    public int allocNidAndInsertSignatureForPost(long pid, String signature) {
        SignatureDaoInfo signatureDaoInfo = new SignatureDaoInfo();
        signatureDaoInfo.setSignature(signature);
        signatureDaoInfo.setPid(pid);
        signatureDaoInfo.setNid(0);
        signatureDao.insertSignature(signatureDaoInfo);
        tryAddToUser(pid, signature);
        return 0;
    }

    @Transactional(rollbackFor = {DaoException.class, RuntimeException.class})
    public int allocNidAndInsertSignatureForReply(long pid, String signature) {
        int nid = signatureDao.selectMaxNidByPid(pid) + 1;
        SignatureDaoInfo signatureDaoInfo = new SignatureDaoInfo();
        signatureDaoInfo.setNid(nid);
        signatureDaoInfo.setPid(pid);
        signatureDaoInfo.setSignature(signature);
        signatureDao.insertSignature(signatureDaoInfo);
        tryAddToUser(pid, signature);
        return nid;
    }

    @Transactional(rollbackFor = {DaoException.class, RuntimeException.class})
    public int ensureNidForReply(long pid, String signature) {
        if (!signatureService.contain(pid, signature)) {
            return signatureService.allocNidAndInsertSignatureForReply(pid, signature);
        }
        else {
            return signatureDao.selectNidByIdAndSignature(pid, signature);
        }
    }

    public List<String> getUsedSignatureForPost(long uid, long pid) {
        List<UserSignatureDaoInfo> res = userSignatureDao.selectAllSignatureById(uid, pid);
        ArrayList<String> ret = new ArrayList<>();
        for (UserSignatureDaoInfo info : res) {
            ret.add(info.getSignature());
        }
        return ret;
    }

    public Map<Long, ArrayList<String>> getUsedSignatureForUser(long uid) {
        List<UserSignatureDaoInfo> res = userSignatureDao.selectAllSignatureByUid(uid);
        HashMap<Long, ArrayList<String>> ret = new HashMap<>();
        for (UserSignatureDaoInfo info : res) {
            ret.merge(info.getPid(), new ArrayList<String>(){{add(info.getSignature());}},
                    (ArrayList<String> l1, ArrayList<String> l2) -> {
                        l1.addAll(l2);
                        return l1;
                    }
            );
        }
        return ret;
    }

    public void addSignatureForUser(long uid, long pid, String signature) {
        UserSignatureDaoInfo userSignatureDaoInfo = new UserSignatureDaoInfo();
        userSignatureDaoInfo.setPid(pid);
        userSignatureDaoInfo.setTime(new Date());
        userSignatureDaoInfo.setUid(uid);
        userSignatureDaoInfo.setSignature(signature);
        userSignatureDao.insertUserSignature(userSignatureDaoInfo);
    }

    public void deleteSignatureForUser(long uid, long pid, String signature) {
        userSignatureDao.deleteUserSignature(uid, pid, signature);
    }

    public String getSignatureById(long pid, long nid) {
        return signatureDao.selectSignatureById(pid, nid);
    }
}
